package edu.isistan.matching.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.wsdl.Binding;
import javax.wsdl.Definition;
import javax.wsdl.Fault;
import javax.wsdl.Operation;
import javax.wsdl.Port;
import javax.wsdl.PortType;
import javax.wsdl.Service;
import javax.wsdl.Types;
import javax.wsdl.WSDLException;
import javax.wsdl.extensions.ElementExtensible;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.UnknownExtensibilityElement;
import javax.wsdl.extensions.http.HTTPBinding;
import javax.wsdl.extensions.schema.Schema;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.wsdl.extensions.soap.SOAPBinding;
import javax.wsdl.extensions.soap12.SOAP12Address;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;
import javax.xml.namespace.QName;

import org.apache.log4j.Logger;
import org.jdom.Attribute;
import org.jdom.input.DOMBuilder;
import org.w3c.dom.Element;

import edu.isistan.matching.structures.elements.BindingElement;
import edu.isistan.matching.structures.elements.MessageElement;
import edu.isistan.matching.structures.elements.OperationElement;
import edu.isistan.matching.structures.elements.PartElement;
import edu.isistan.matching.structures.elements.PortTypeElement;
import edu.isistan.matching.structures.elements.ServiceElement;
import edu.isistan.matching.structures.elements.TypeElement;
import edu.isistan.matching.structures.elements.WSDLDefinitions;
import edu.isistan.matching.structures.elements.types.AnyType;
import edu.isistan.matching.structures.elements.types.ComplexType;
import edu.isistan.matching.structures.elements.types.ElementType;
import edu.isistan.matching.structures.elements.types.PrimitiveType;
import edu.isistan.matching.structures.elements.types.SimpleType;

public class WSDLLoader {

	/**
	 * Estructura que contiene los tipos definidos en el esquema.
	 */
	private Map<String, TypeElement> typesHash = new HashMap<String, TypeElement>();

	/**
	 * Lista que posee los tipos que contienen referencias a tipos no resueltos.
	 */

	private Map<ComplexType, String> complexContentHash = new HashMap<ComplexType, String>();

	/**
	 * Lista de elementos, no incluidos dentro de un tipo de datos complejo, que
	 * hacen referencia a un tipo de datos complejos a través de su atributo
	 * type
	 */
	private List<TypeElement> fromElementUnresolve = new ArrayList<TypeElement>();

	/**
	 * Lista de elementos pertenecientes a una estructura ComplexType que hacen
	 * referencia a un tipo de dato complejos
	 */
	private List<TypeElement> fromComplexTypeUnresolve = new ArrayList<TypeElement>();

	private Map<QName, List> operationForPortType = new HashMap<QName, List>();

	static Logger logger = Logger.getLogger(WSDLDefinitions.class);

	public WSDLLoader() {
	}

	/**
	 * Retorna una representación WSDL dado un archivo WSDL
	 * 
	 * @param path path del archivo WSDL
	 * @return representacion WSDL correspondiente al archivo WSDL pasado por parámetro
	 */
	public WSDLDefinitions getDefinition(String path) {
		typesHash.clear();
		fromComplexTypeUnresolve.clear();
		fromElementUnresolve.clear();
		complexContentHash.clear();
		operationForPortType.clear();

		WSDLFactory wsdlFactory = null;
		WSDLReader wsdlReader = null;
		Definition definition = null;
		WSDLDefinitions definitionElement = null;
		try {
			wsdlFactory = WSDLFactory.newInstance();
			wsdlReader = wsdlFactory.newWSDLReader();
			wsdlReader.setFeature("javax.wsdl.verbose", false);
			//wsdlReader.setFeature("javax.wsdl.importDocuments", false );
			definition = wsdlReader.readWSDL(path);
			if (definition != null) {
				createTypes(definition);
				definitionElement = new WSDLDefinitions(path);
				definitionElement.setNameSpaces(definition.getNamespaces());
				definitionElement.setTargetNamespace(definition.getTargetNamespace());
				Map portTypeMap = createOperationsFromDefinition(definition,definitionElement);
				createBindingElements(definition, definitionElement,portTypeMap);
				createServiceElement(definition, definitionElement);
			}
		} catch (WSDLException e) {
			logger.error("Error parseando un archivo WSDL", e);
		} finally {
			wsdlReader = null;
		}
		return definitionElement;
	}

	/**
	 * Este método analiza el schema contenido en el WSDL. En primer lugar, se
	 * recupera todos los tipos existente. Luego se resuelven las dependencias
	 * entre ellos.
	 * 
	 * @param definition
	 */
	private void createTypes(Definition definition) {
		// Primero, se crea la representacion para los Types encontrados en el
		// esquema
		findTypes(definition.getTypes());

		// Intentamos resolver las dependencias entre Complex. Es decir,
		// elementos de un complejo hacen referencia a otros tipos de datos
		// complejo.

		int i = 0;
		while (i < fromComplexTypeUnresolve.size()) {
			TypeElement typeElement = fromComplexTypeUnresolve.get(i);
			i++;
			boolean resolverDependencia = true;

			String typeString = typeElement.getType();
			TypeElement typeReferenciado = getTypesHash().get(typeString);

			if (typeReferenciado != null) {
				getLogger().debug(
						"Resolviendo type exitosamente de: "
								+ typeElement.getName() + " de tipo "
								+ typeString);
				if (fromComplexTypeUnresolve.contains(typeReferenciado)) {

					int posicionType = fromComplexTypeUnresolve
							.lastIndexOf(typeElement);
					int posicionRef = fromComplexTypeUnresolve
							.lastIndexOf(typeReferenciado);
					if (posicionType < posicionRef) {
						resolverDependencia = false;
						fromComplexTypeUnresolve.add(typeElement);
					}
				}
				if (resolverDependencia) {
					if (typeReferenciado instanceof ElementType) {
						copyElementType(typeElement,
								(ElementType) typeReferenciado);
					} else {
						typeElement.getSubTiposList().add(typeReferenciado);
					}
					String newType = getOriginalType(typeReferenciado);
					typeElement.setType(newType);
					giveNilliableToChild(typeReferenciado, typeElement
							.isNillable());
				}
			} else {
				getLogger().debug(
						"Error: No se pudo resolver el tipo " + typeString);

			}

		}
		// Completamos el manejo de ComplexContent
		Set<ComplexType> keyIterator = complexContentHash.keySet();
		for (Iterator<ComplexType> iterator = keyIterator.iterator(); iterator
				.hasNext();) {
			TypeElement typeElementComplexContent = iterator.next();
			String base = complexContentHash.get(typeElementComplexContent);
			String[] typeAndNamespace = getTypeString(base);

			TypeElement referenciado = typesHash.get(typeAndNamespace[0]);

			if (referenciado != null && referenciado.getSubTiposList() != null) {
				List<TypeElement> children = referenciado.getSubTiposList();
				for (Iterator<TypeElement> childRefIterator = children
						.iterator(); childRefIterator.hasNext();) {
					TypeElement childRef = childRefIterator.next();
					TypeElement clone = (TypeElement) childRef.clone();
					typeElementComplexContent.getSubTiposList().add(clone);
					clone.setParentElement(typeElementComplexContent);

				}
			}
		}
		// Resolvemos las dependecias de Elementos a Complex, clonando a esos
		// �ltimos.
		for (TypeElement typeElement : fromElementUnresolve) {
			String typeString = typeElement.getType();
			TypeElement typeReferenciado = getTypesHash().get(typeString);
			if (typeReferenciado != null) {
				getLogger().debug(
						"Resolviendo type exitosamente de: "
								+ typeElement.getName() + " de tipo "
								+ typeString);
				TypeElement clone = (TypeElement) typeReferenciado.clone();
				typeElement.getSubTiposList().add(clone);
				clone.setParentElement(typeElement);
				giveNilliableToChild(clone, typeElement.isNillable());

			} else {
				getLogger().debug(
						"Error: No se pudo resolver el tipo " + typeString);
			}
		}
	}

	/**
	 * Se copia los hijos de un elemento a otro, y adem�s se indica si es lista
	 * o arreglo. Este m�todo se utiliza para eliminar instancias anidadas de
	 * elementos ElementType, las cuales no a�aden informaci�n �til.
	 * 
	 */

	private void copyElementType(TypeElement element, ElementType referenciado) {
		element.setLista(referenciado.isLista());
		element.setNillable(referenciado.isNillable());

		for (Iterator iterator = referenciado.getSubTiposList().iterator(); iterator
				.hasNext();) {
			TypeElement hijo = (TypeElement) iterator.next();
			element.getSubTiposList().add(hijo);
			hijo.setParentElement(element);
			hijo.setNillable(element.isNillable());
		}
	}

	/**
	 * Cuando se utiliza el estilo Wrapper, ante un Array � lista se crea un
	 * complejo de nombre ArrayOfX, donde X es el tipo del array. Cuando se
	 * referencia a un complejo de este tipo, se necesita el nombre del tipo de
	 * datos"real" del arreglo.
	 * 
	 * @param referenciado
	 * @return Tipo de datos del elemento pasado por parametro.
	 */
	private String getOriginalType(TypeElement element) {
		if (element.getName().startsWith("ArrayOf")) {
			return element.getType();
		}
		return element.getName();
	}

	private void giveNilliableToChild(TypeElement element, boolean nillable) {

		if (element.getName().startsWith("ArrayOf")) {
			if (element.getSubTiposList() != null) {
				{
					for (Iterator iterator = element.getSubTiposList()
							.iterator(); iterator.hasNext();) {
						TypeElement hijo = (TypeElement) iterator.next();
						hijo.setNillable(nillable);
					}
				}
			} else {
				element.setNillable(nillable);
			}
		}
	}

	private void createBindingElements(Definition definition,
			WSDLDefinitions definitionElement,
			Map<QName, PortTypeElement> portTypeMap) {

		List<OperationElement> operationsResult = new ArrayList<OperationElement>();
		Map bindingMap = definition.getAllBindings();
		for (Iterator iterator = bindingMap.keySet().iterator(); iterator
				.hasNext();) {
			QName key = (QName) iterator.next();
			Binding value = (Binding) bindingMap.get(key);

			boolean soapBinding = isSoapBinding(value);
			// List operaciones4Port =
			// operationForPortType.get(value.getPortType().getQName());
			if (soapBinding) {
				List operaciones4Port = operationForPortType.get(value
						.getPortType().getQName());

				getLogger().debug(
						"Agregando operaciones soap: "
								+ operaciones4Port.size());
				operationsResult.addAll(operaciones4Port);
				BindingElement elementBin = new BindingElement(key
						.getLocalPart(), key.getNamespaceURI());
				elementBin.setParentElement(definitionElement);

				PortTypeElement portType = portTypeMap.get(value.getPortType()
						.getQName());
				elementBin.setPortType(portType);

				definitionElement.getBindingList().add(elementBin);

			} else {
				// getLogger().debug("Descartando operaciones http: " +
				// operaciones4Port.size());
			}

		}
		definitionElement.setOperationsList(operationsResult);
	}

	/**
	 * Indica si el elemento Binding pasado por par�metro es de tipo Soap o no
	 * lo es.
	 * 
	 * @param bind
	 *            Tag Binding a analizar
	 * @return <b>true si</b> el binding es Soap. <b>false</b> en caso
	 *         contrario.
	 */
	private boolean isSoapBinding(Binding bind) {
		List extElements = bind.getExtensibilityElements();
		for (Iterator iterator = extElements.iterator(); iterator.hasNext();) {
			javax.wsdl.extensions.ExtensibilityElement el = (javax.wsdl.extensions.ExtensibilityElement) iterator
					.next();
			if (el instanceof HTTPBinding) {
				getLogger().debug("Binding HTTP");
				return false;

			}
			if (el instanceof SOAPBinding) {
				getLogger().debug("Binding SOAP");
				return true;

			}

		}
		getLogger().debug("Binding desconocido");

		return false;
	}

	/**
	 * Crea los elementos que representen Servicios contenidos en el archivo
	 * WSDL y los almacena dentro del objeto WSDLDefinition pasado por par�metro
	 * 
	 * @param definition
	 * @param definitionElement
	 */
	private void createServiceElement(Definition definition,
			WSDLDefinitions definitionElement) {
		Map serviceMap = definition.getAllServices();
		for (Iterator iterator = serviceMap.keySet().iterator(); iterator
				.hasNext();) {
			QName key = (QName) iterator.next();
			Object value = serviceMap.get(key);
			ServiceElement serviceElement = new ServiceElement(key
					.getLocalPart(), key.getNamespaceURI());
			definitionElement.getServiceList().add(serviceElement);

			Service service = (Service) value;
			Map ports = service.getPorts();
			for (Iterator iterator2 = ports.keySet().iterator(); iterator2
					.hasNext();) {
				String name = (String) iterator2.next();
				Port port = (Port) ports.get(name);
				BindingElement bindingElement = getBindingElementFromDefinition(
						definitionElement, port.getBinding());
				if (bindingElement != null) {
					for (Iterator iterator3 = port.getExtensibilityElements()
							.iterator(); iterator3.hasNext();) {
						Object obj = (Object) iterator3.next();
						if (obj instanceof SOAP12Address) {
							SOAP12Address addressElement = (SOAP12Address) obj;
							bindingElement.getBindingAddresses().add(
									addressElement.getLocationURI());
						}
						if (obj instanceof SOAPAddress) {
							SOAPAddress addressElement = (SOAPAddress) obj;

							bindingElement.getBindingAddresses().add(
									addressElement.getLocationURI());
						}
					}

				}

			}

		}
	}

	private BindingElement getBindingElementFromDefinition(
			WSDLDefinitions definitionElement, Binding bin) {
		for (Iterator iterator = definitionElement.getBindingList().iterator(); iterator
				.hasNext();) {
			BindingElement element = (BindingElement) iterator.next();
			if (element.getName().equals(bin.getQName().getLocalPart())
					&& element.getBindingNameSpace().equals(
							bin.getQName().getNamespaceURI()))
				return element;
		}
		return null;
	}

	/**
	 * Obtenemos la lista de Operaciones a partir de un objeto Definition. Se
	 * recorren todos los TypeElement portType y se extraen las operaciones
	 * contenidas en ellos. Retorna una estructura Map con los PortTypes
	 * existentes en el Definition.
	 */
	private Map<QName, PortTypeElement> createOperationsFromDefinition(Definition def, WSDLDefinitions definitionElement) {
		Map<QName, PortTypeElement> portTypeMap = new HashMap<QName, PortTypeElement>();
		List<OperationElement> operationsResult = new ArrayList<OperationElement>();
		if (def != null) {
			if (def.getPortTypes() != null) {
				Iterator<PortType> portTypeIterator = def.getPortTypes().values().iterator();
				while (portTypeIterator.hasNext()) {
					PortType portType = portTypeIterator.next();
					PortTypeElement portTypeElement = new PortTypeElement(portType.getQName().getLocalPart(), portType.getQName().getNamespaceURI());
					portTypeMap.put(portType.getQName(), portTypeElement);
					List<OperationElement> result = getOperationsForPortType(portType, portTypeElement);
					operationsResult.addAll(result);
					operationForPortType.put(portType.getQName(), result);
				}
			}
		}
		return portTypeMap;
	}

	/**
	 * Obtenemos la lista de operaciones contenida dentro de un PortType.
	 * 
	 * @param portType
	 * @return
	 */
	private List<OperationElement> getOperationsForPortType(PortType portType, PortTypeElement portTypeElement) {

		List<OperationElement> resultadoOperacionesList = new ArrayList<OperationElement>();
		Iterator<Operation> opIterator = portType.getOperations().iterator();

		while (opIterator.hasNext()) {
			Operation op = opIterator.next();
			OperationElement operacionObtenida = createOperationElement(op);
			operacionObtenida.setPortType(portTypeElement);
			resultadoOperacionesList.add(operacionObtenida);
		}
		return resultadoOperacionesList;
	}

	/**
	 * Se crea un objeto que representa un Operacion de wsdl
	 * 
	 * @param operacion
	 * @return
	 */
	private OperationElement createOperationElement(Operation xOperation) {
		MessageElement mensajeIn = null;
		// Extraer Input
		if (xOperation.getInput() != null) {
			if (xOperation.getInput().getMessage() != null) {
				mensajeIn = createMessageElement(xOperation.getInput().getMessage());
				mensajeIn.setTypeMessage(MessageElement.IN_MESSAGE);
			}
		}
		// Extraer output
		MessageElement mensajeOut = null;
		if (xOperation.getOutput() != null) {
			if (xOperation.getOutput().getMessage() != null) {
				mensajeOut = createMessageElement(xOperation.getOutput().getMessage());
				mensajeOut.setTypeMessage(MessageElement.OUT_MESSAGE);
			}
		}
		// Extraer faults
		Map faultMessagesMap = xOperation.getFaults();
		Set faultSet = faultMessagesMap.keySet();
		List<MessageElement> faultMessagesList = new ArrayList<MessageElement>();
		for (Iterator iterator = faultSet.iterator(); iterator.hasNext();) {
			Object key = (Object) iterator.next();
			Fault fault = (Fault) xOperation.getFaults().get(key);
			// TODO: Mensaje Fault
			// MessageElement faultMessageElement =
			// createMessageElement(fault.getMessage());
			// faultMessagesList.add(faultMessageElement);
		}

		OperationElement operacion = new OperationElement(xOperation.getName());
		operacion.setInMessage(mensajeIn);
		operacion.setOutMessage(mensajeOut);
		operacion.setFaultMessages(faultMessagesList);
		return operacion;
	}

	/**
	 * Crea una representación de un Mensaje de acuerdo a un Tag Message
	 * pasado por parámetro.
	 * 
	 * @param msg
	 * @return
	 */
	private MessageElement createMessageElement(javax.wsdl.Message xMessage) {
		String msgName = xMessage.getQName().getLocalPart();
		List<PartElement> partsList = getPartList(xMessage);
		MessageElement msgElement = new MessageElement(msgName);
		msgElement.setPartsList(partsList);
		return msgElement;
	}

	/**
	 * Retorna los distintos parametros de un Tag Message WSDL pasado por
	 * par�metro.
	 * 
	 * @param msg
	 * @return
	 */
	private List<PartElement> getPartList(javax.wsdl.Message msg) {
		List<PartElement> resultadoPartsList = new ArrayList<PartElement>();
		List<javax.wsdl.Part> partOrd = msg.getOrderedParts(null);
		Iterator<javax.wsdl.Part> partIterator = partOrd.iterator();
		while (partIterator.hasNext()) {
			javax.wsdl.Part part = partIterator.next();
			PartElement partElement = createPartElement(part);
			if (partElement != null) {
				resultadoPartsList.add(partElement);
			}
		}
		return resultadoPartsList;
	}

	/**
	 * Crea un elemento que representa un PART de un mensaje wsdl.
	 * 
	 * @param part
	 * @return
	 */
	private PartElement createPartElement(javax.wsdl.Part part) {

		String typeString = null;
		String nameSpaceString = null;
		TypeElement typeElement = null;

		if (part.getTypeName()!=null) {
			typeString = part.getTypeName().getLocalPart();
			nameSpaceString = part.getTypeName().getNamespaceURI();
		} else {
			if (part.getElementName() != null) {
				typeString = part.getElementName().getLocalPart();
				nameSpaceString = part.getElementName().getNamespaceURI();
			}
		}

		if (typeString != null) {
			typeElement = findTypeForPart(typeString, nameSpaceString, part.getName());
		} else {
			getLogger().debug("El part " + part.getName() + " no posee atributo type ni atributo element");
		}

		PartElement resultPart = new PartElement(part.getName());

		// seteamos el padre del Type:
		if (typeElement == null) {
			return null;
		}
		typeElement.setParentElement(resultPart);
		resultPart.setType(typeElement);

		return resultPart;
	}

	/**
	 * Se retorna un tipo de datos (representado por TypeElement) que
	 * corresponde al tipo de datos referenciado desde un tag Part a trav�s del
	 * atributo Type.
	 * 
	 * @param typeName
	 *            valor del atributo Type del Part, el cual tambien debe
	 *            coincidir con el nombre del tipo de datos que estamos buscando
	 * @param nameSpace
	 *            namespace del tipo de datos que se est� buscando
	 * @param partName
	 *            nombre del part
	 * @return elemento que representa el tipo de dato referenciado por el Part
	 */
	private TypeElement findTypeForPart(String typeName, String nameSpace, String partName) {

		boolean isPrimitivo = TypeElement.PRIMITIVE_TYPES.contains(typeName);
		if (isPrimitivo) {
			return createPrimitiveType(typeName, nameSpace, partName, false, false);
		} else {
			boolean isAnyType = TypeElement.ANY_TYPE.equals(typeName);
			if (isAnyType) {
				return createAnyType(typeName, nameSpace, partName, false, false);
			}
			// Si no es primitivo ni anyObject, buscamos el tipo en la tabla de
			// hash
			TypeElement typeElement = getTypesHash().get(typeName);
			return typeElement;
		}
	}

	/**
	 * Se descubren los distintos tipos de datos contenidos dentro del schema
	 * especificado en el documento WSDL analizado. Los tipos descubiertos son
	 * almacenados en la estructura hash con nombre <b>typesHash</b>
	 * 
	 * @param types
	 *            Representación del tag Types del documento WSDL parseado
	 */
	private void findTypes(Types types) {

		List<ExtensibilityElement> schemas = findExtensibilityElement(types, "schema");
		getLogger().debug("Cantidad de schemas: " + schemas.size());
		for (ExtensibilityElement schemaExtElem : schemas) {
			Element schemaElement;
			if (schemaExtElem instanceof UnknownExtensibilityElement) {
				schemaElement = ((UnknownExtensibilityElement) schemaExtElem).getElement();
			} else {
				schemaElement = ((Schema) schemaExtElem).getElement();
			}
			DOMBuilder domBuilder = new DOMBuilder();
			org.jdom.Element jdomSchemaElement = domBuilder.build(schemaElement);

			try {
				String schemaTargetNameSpace = jdomSchemaElement.getAttributeValue("targetNamespace");
				List childElementsList = jdomSchemaElement.getChildren();
				if (childElementsList != null) {
					for (Object o : childElementsList) {
						org.jdom.Element element = (org.jdom.Element) o;
						if (!element.getName().toString().equalsIgnoreCase("import")) {
							processTag(element, schemaTargetNameSpace, false);
						}
					}
				}
			} catch (Exception e) {
				logger.error("Error cargando los tipos de un archivo WSDL", e);
			}
		}

	}

	private TypeElement createArrayElement(org.jdom.Element xElement, String schemaTargetNameSpace, boolean fromComplexType) {

		TypeElement elementArray = null;
		String nameOfComplex = xElement.getAttributeValue("name");
		String nillable = xElement.getAttributeValue("nillable");
		boolean isNillable = (nillable != null && "true".equals(nillable));
		if (nameOfComplex.startsWith("ArrayOfAnyType")) {
			elementArray = createElementForArray("anyType", "", nameOfComplex,
					isNillable, true, schemaTargetNameSpace, false);
			return elementArray;
		}

		if (xElement.getChildren().size() == 1) {
			org.jdom.Element complexContentChild1 = (org.jdom.Element) xElement
					.getChildren().get(0);
			if (complexContentChild1.getName().toString().equalsIgnoreCase(
					"complexContent")
					&& complexContentChild1.getChildren().size() == 1) {
				{
					org.jdom.Element restriction2 = (org.jdom.Element) complexContentChild1
							.getChildren().get(0);
					if (restriction2.getName().toString().equalsIgnoreCase(
							"restriction")
							&& restriction2.getChildren().size() == 1) {
						org.jdom.Element attribute3 = (org.jdom.Element) restriction2
								.getChildren().get(0);

						String arrayTypeString = getValueAttribute(attribute3,
								"arrayType");
						String[] arrayType = getTypeString(arrayTypeString);
						String typeString = "";
						String nameSpace = "";
						nameSpace = arrayType[1];
						typeString = arrayType[0];
						typeString = typeString.replace("[]", "");

						elementArray = createElementForArray(typeString,
								nameSpace, nameOfComplex, isNillable, true,
								schemaTargetNameSpace, fromComplexType);
						return elementArray;

					}
				}
			} else {
				if (complexContentChild1.getName().toString().equalsIgnoreCase(
						"sequence")
						&& complexContentChild1.getChildren().size() == 1) {

					org.jdom.Element element2 = (org.jdom.Element) complexContentChild1
							.getChildren().get(0);
					if (element2.getName().toString().equalsIgnoreCase(
							"element")
							&& element2.getChildren().size() == 0
							&& !"".equals(element2
									.getAttributeValue("maxOccurs"))) {

						// String arrayTypeString = getValueAttribute(element2,
						// "type");
						String arrayTypeString = getTypeOrRefValueAttribute(element2);

						String[] arrayType = getTypeString(arrayTypeString);
						String typeString = "";
						String nameSpace = "";
						nameSpace = arrayType[1];
						typeString = arrayType[0];

						elementArray = createElementForArray(typeString,
								nameSpace, nameOfComplex /* nameString */,
								isNillable, false, schemaTargetNameSpace,
								fromComplexType);
						elementArray.setLista(true);
						return elementArray;

					}
				}

			}
		}
		getLogger()
				.debug(
						"Error En generación de arreglo: se asume que es un tipo complejo que comienza con el nombre ArrayOf....");
		return null;
	}

	private String getTypeOrRefValueAttribute(org.jdom.Element attribute) {
		String value = getValueAttribute(attribute, "type");
		if (value != null && !"".equals(value.trim())) {
			return value;
		} else {
			logger.debug("Buscando atributo Ref");
			return getValueAttribute(attribute, "ref");
		}

	}

	private String getValueAttribute(org.jdom.Element attribute, String nameAt) {
		String arrayTypeString = attribute.getAttributeValue(nameAt);
		if (arrayTypeString == null) {
			for (Object obj : attribute.getAttributes()) {
				Attribute attributeOb = (Attribute) obj;
				if (attributeOb.getName().equals(nameAt)) {
					arrayTypeString = attributeOb.getValue();
				}
			}

		}
		return arrayTypeString;
	}

	private TypeElement createElementForArray(String typeString,
			String nameSpace, String nameString, boolean isNillable,
			boolean isSecuencia, String schemaTargetNameSpace,
			boolean fromComplexType) {
		boolean isPrimitivo = TypeElement.PRIMITIVE_TYPES.contains(typeString);
		boolean isAnyType = TypeElement.ANY_TYPE.equals(typeString);
		TypeElement elementArray = null;
		if (isAnyType) {
			elementArray = createAnyType(typeString, nameSpace, nameString,
					isNillable, isSecuencia);
		} else {
			if (isPrimitivo) {
				elementArray = createPrimitiveType(typeString, nameSpace,
						nameString, isNillable, true);
			} else {

				elementArray = createElementType(nameString, typeString,
						nameSpace, schemaTargetNameSpace, isNillable,
						isSecuencia, true /* fromComplexType */);

			}
		}
		getTypesHash().put(nameString, elementArray);
		return elementArray;
	}

	/**
	 * El m�todo procesa un tag de la especificaci�n: Puede ser un Element,
	 * ComplexType, SimpleType.
	 * 
	 * @param xElement
	 *            Elemento que representa el tag a procesar.
	 * @param schemaTargetNameSpace
	 * @param fromComplexType
	 *            Indica si el elemento esta incluido dentro de un tipo de datos
	 *            complejo.
	 * @return Representaci�n del Tag procesado.
	 */
	private TypeElement processTag(org.jdom.Element xElement,
			String schemaTargetNameSpace, boolean fromComplexType) {

		TypeElement typeElement = null;
		try {

			if (xElement.getName().toString().equalsIgnoreCase("element")) {

				typeElement = createElement(xElement, schemaTargetNameSpace,
						fromComplexType);
				return typeElement;
			}
			String attributeName = xElement.getAttributeValue("name");
			if (xElement.getName().toString().equalsIgnoreCase("complexType")
					&& attributeName != null
					&& attributeName.startsWith("ArrayOf")) {

				typeElement = createArrayElement(xElement,
						schemaTargetNameSpace, fromComplexType);
				// Si typeElement es nulo, corresponde al caso que un
				// complexType empiece con ArrayOf debido a que arrastra el
				// nombre desde la aplicación, y precisamente no corresponde a
				// un array. Por lo tanto se continua el analisis
				if (typeElement != null) {
					return typeElement;
				}
			}
			if (xElement.getName().toString().equalsIgnoreCase("complexType")
					|| xElement.getName().toString().equalsIgnoreCase("group")) {

				typeElement = crearComplexType(xElement, schemaTargetNameSpace,
						fromComplexType);
				return typeElement;
			}

			if (xElement.getName().toString().equalsIgnoreCase("simpleType")) {
				typeElement = createSimpleType(xElement, schemaTargetNameSpace);
				return typeElement;
			}

			// TODO VER el TypeElement Attribute
			// Si no entra en ningun if, estamos frente a un TypeElement
			// desconocido
			getLogger().debug(
					"Elemento desconocido: " + xElement.getName().toString());

		} catch (Exception e) {
			getLogger().debug(
					"Error procesando elemento ("
							+ xElement.getAttributeValue("name") + ")", e);
		}
		return null;
	}

	private SimpleType createSimpleType(org.jdom.Element xElement,
			String schemaTargetNameSpace) {

		SimpleType simpleType = new SimpleType();
		String simpleTypeName = xElement.getAttributeValue("name");
		simpleType.setName(simpleTypeName);
		simpleType.setTargetNameSpace(schemaTargetNameSpace);
		String nillable = xElement.getAttributeValue("nillable");
		String secuencia = xElement.getAttributeValue("maxOccurs");
		if (nillable != null && "true".equals(nillable)) {
			simpleType.setNillable(true);
		}
		if (secuencia != null && "unbounded".equals(secuencia)) {
			simpleType.setSecuencia(true);
		}

		for (int i = 0; i < xElement.getChildren().size(); i++) {
			org.jdom.Element simpleTypeChild = (org.jdom.Element) xElement
					.getChildren().get(i);
			if (simpleTypeChild.getName().equalsIgnoreCase("restriction")) {
				String base = simpleTypeChild.getAttributeValue("base");
				String[] typeAndNamespace = getTypeString(base);
				PrimitiveType primitive = new PrimitiveType();
				primitive.setName("value");
				primitive.setNameSpace(typeAndNamespace[1]);
				primitive.setType(typeAndNamespace[0]);
				simpleType.addChild(primitive);

			} else {
				getLogger().error(
						"Elemento hijo de Simple Type no contemplado hasta el momento: "
								+ simpleTypeChild.getName());
			}

		}
		typesHash.put(simpleTypeName, simpleType);
		return simpleType;
	}

	private TypeElement createElement(org.jdom.Element xElement,
			String schemaTargetNameSpace, boolean fromComplexType) {

		if (xElement.getChildren().size() > 0) {

			ElementType elementTag = new ElementType();

			org.jdom.Element xChild = (org.jdom.Element) xElement.getChildren()
					.get(0);
			TypeElement childTypeElement = processTag(xChild,
					schemaTargetNameSpace, fromComplexType);
			if (childTypeElement == null) {
				return null;
			}

			elementTag.setPrimitivo(false);
			elementTag.setType(null);
			elementTag.setTargetNameSpace(schemaTargetNameSpace);
			String nameElement = xElement.getAttributeValue("name");

			String nillable = xElement.getAttributeValue("nillable");
			String secuencia = xElement.getAttributeValue("maxOccurs");
			if (nillable != null && "true".equals(nillable)) {
				elementTag.setNillable(true);
			}
			if (secuencia != null && "unbounded".equals(secuencia)) {
				elementTag.setSecuencia(true);
			}
			elementTag.setName(nameElement);
			if (childTypeElement != null) {
				elementTag.addChild(childTypeElement);
			}
			if (elementTag != null && nameElement != null
					&& !"".equals(nameElement.trim())) {
				typesHash.put(nameElement, elementTag);
			}

			return elementTag;

		} else {

			// El tag Element no tiene hijos
			String atributeTypeString = xElement.getAttributeValue("type");

			if (atributeTypeString != null
					&& !"".equals(atributeTypeString.trim())) {
				// El tag Element posee el atributo "TYPE"
				// Puede tener referencia a un tipo primitivo o no (a un
				// complexType)

				String[] typesNames = getTypeString(atributeTypeString);
				String typeString = "";
				String nameSpace = "";
				nameSpace = typesNames[1];
				typeString = typesNames[0];

				String nameString = xElement.getAttributeValue("name");
				// verificamos que sea type Primitivo.
				boolean isPrimitivo = TypeElement.PRIMITIVE_TYPES
						.contains(typeString);
				boolean isAnyType = TypeElement.ANY_TYPE.equals(typeString);

				String nillable = xElement.getAttributeValue("nillable");
				String secuencia = xElement.getAttributeValue("maxOccurs");
				boolean isNillable = (nillable != null && "true"
						.equals(nillable));
				boolean isSecuencia = (secuencia != null && "unbounded"
						.equals(secuencia));

				if (isAnyType) {
					return createAnyType(typeString, nameSpace, nameString,
							isNillable, isSecuencia);
				} else {
					if (isPrimitivo) {

						return createPrimitiveType(typeString, nameSpace,
								nameString, isNillable, isSecuencia);

					} else {
						return createElementType(nameString, typeString,
								nameSpace, schemaTargetNameSpace, isNillable,
								isSecuencia, fromComplexType);
					}
				}
			} else {
				// El tag Element NO posee atributo "TYPE"
				// TODO: casos no contemplados hasta el momento: por ejemplo:
				// <s:element ref="s:schema" />
				String atributeRefString = xElement.getAttributeValue("ref");
				if (atributeRefString != null
						&& !"".equals(atributeRefString.trim())) {
					getLogger().debug("Casos no contemplados hasta el momento: "
									+ xElement);
					return createAnyType(TypeElement.ANY_TYPE, "",
							"anyTypeName", false, false);

				} else {
					getLogger().debug("Elemento erroneo: sin atributo Type");
				}
				return null;

			}

		}

	}

	/**
	 * Se crea un elemento que corresponde a un Tag Element: ya sea contenido
	 * dentro del esquema o bien dentro de un complejo
	 * 
	 * @param nameString
	 * @param typeName
	 * @param nameSpace
	 * @param schemaTargetNameSpace
	 * @param isNillable
	 * @param isSecuencia
	 * @param fromComplexType
	 *            Indica si el tag element está contenido en un complejo (es
	 *            decir, es una propiedad del tipo de datos complejo)
	 * @return
	 */
	private ElementType createElementType(String nameString, String typeName,
			String nameSpace, String schemaTargetNameSpace, boolean isNillable,
			boolean isSecuencia, boolean fromComplexType) {

		ElementType elementTag = new ElementType(nameString);
		elementTag.setPrimitivo(false);
		elementTag.setType(typeName);
		elementTag.setNameSpace(nameSpace);
		elementTag.setTargetNameSpace(schemaTargetNameSpace);
		elementTag.setNillable(isNillable);
		elementTag.setSecuencia(isSecuencia);

		if (fromComplexType) {
			fromComplexTypeUnresolve.add(elementTag);
		} else {
			fromElementUnresolve.add(elementTag);
		}
		return elementTag;
	}

	private PrimitiveType createPrimitiveType(String typeName,
			String nameSpace, String nameString, boolean isNillable,
			boolean isSecuencia) {

		PrimitiveType primitivo = new PrimitiveType();
		primitivo.setName(nameString);
		primitivo.setType(typeName);
		primitivo.setNameSpace(nameSpace);
		primitivo.setPrimitivo(true);
		primitivo.setNillable(isNillable);
		primitivo.setSecuencia(isSecuencia);
		return primitivo;

	}

	private AnyType createAnyType(String typeName, String nameSpace,
			String nameString, boolean isNillable, boolean isSecuencia) {

		AnyType any = new AnyType();
		any.setName(nameString);
		any.setType(typeName);
		any.setNameSpace(nameSpace);
		any.setNillable(isNillable);
		any.setSecuencia(isSecuencia);
		return any;

	}

	private TypeElement crearComplexType(org.jdom.Element xElement,
			String schemaTargetNameSpace, boolean fromComplexType) {

		ComplexType complexType = new ComplexType();
		String complexName = xElement.getAttributeValue("name");
		complexType.setTargetNameSpace(schemaTargetNameSpace);
		complexType.setName(complexName);
		if (complexType.getName() != null && !"".equals(complexType.getName())) {
			fromComplexType = true;
		} else {
			if (xElement.getChildren() == null
					|| xElement.getChildren().size() == 0) {
				return null;
			}
		}

		// Debiera tener un solo hijo...
		for (int i = 0; i < xElement.getChildren().size(); i++) {
			org.jdom.Element child = (org.jdom.Element) xElement.getChildren()
					.get(i);

			if (child.getName().toString().equalsIgnoreCase("complexContent")) {
				Boolean isExtension = isComplexContentExtension(child);
				if (isExtension != null) {
					if (isExtension) {
						processComplexContentExtension(complexType, child);
					} else {
						return processComplexContentRestriction(complexType,
								child);
					}
				} else {
					logger.error("ComplexContext no contemplado");
				}
			} else {
				// Analizamos si el hijo será un secuence,all...
				if (hasComplexTypeVariety(child)) {

					analyzeComplexTypeVariety(complexType, child,
							schemaTargetNameSpace, fromComplexType);

				} else {
					if (child.getName().equalsIgnoreCase("attribute")
							|| child.getName().equalsIgnoreCase("element")) {
						createAttributes(complexType, child);
					} else {
						getLogger().error(
								"Hijo de ComplexType no contemplado:"
										+ complexType.getName());
					}

				}
			}
		}
		// Agregamos el tipo complejo al hash de resultados:
		// Hay casos que el TypeElement complexType puede tener el atributo
		// 'name' vacio
		// (es decir, no lo posee). En esos casos dicho TypeElement esta
		// contenido en un
		// TypeElement Element, por lo que no hace falta agregarlo al Hash (pues
		// nunca
		// ser� referenciado desde un Parametro)

		if (complexType != null && complexName != null
				&& !"".equals(complexName)) {
			typesHash.put(complexName, complexType);
		}
		return complexType;

	}

	private void createAttributes(ComplexType complexType,
			org.jdom.Element xElement) {
		TypeElement element = createElement(xElement, "", true);
		if (element != null) {
			complexType.addChild(element);
		}

	}

	private void analyzeComplexTypeVariety(ComplexType complexType,
			org.jdom.Element complexChild, String schemaTargetNameSpace,
			boolean fromComplexType) {

		// seteamos el sequence, all,...
		complexType.setType(complexChild.getName().toString());

		// Recorro los hijos y llamo recursivamente
		for (int j = 0; j < complexChild.getChildren().size(); j++) {
			org.jdom.Element complexNieto = (org.jdom.Element) complexChild
					.getChildren().get(j);

			TypeElement typeHijo = processTag(complexNieto,
					schemaTargetNameSpace, fromComplexType);
			// agregar cada hijo al padre. (al complexType,
			// no al secuence o all..)
			if (typeHijo != null) {
				complexType.addChild(typeHijo);
			}

		}
	}

	private Boolean isComplexContentExtension(
			org.jdom.Element elementComplexContent) {
		for (int j = 0; j < elementComplexContent.getChildren().size(); j++) {
			org.jdom.Element elementComplexChild = (org.jdom.Element) elementComplexContent
					.getChildren().get(j);

			if (elementComplexChild.getName().toString().equalsIgnoreCase(
					"extension")) {
				return true;
			} else {
				if (elementComplexChild.getName().toString().equalsIgnoreCase(
						"restriction")) {
					return false;
				}
			}
		}
		getLogger().error("Caso de Content no  reconocido");
		return false;
	}

	private void processComplexContentExtension(ComplexType complexType,
			org.jdom.Element elementComplexContent) {

		for (int j = 0; j < elementComplexContent.getChildren().size(); j++) {
			org.jdom.Element elementComplexChild = (org.jdom.Element) elementComplexContent
					.getChildren().get(j);

			if (elementComplexChild.getName().toString().equalsIgnoreCase(
					"extension")) {

				if (elementComplexChild.getChildren().size() == 1) {
					org.jdom.Element childElement = (org.jdom.Element) elementComplexChild
							.getChildren().get(0);
					if (hasComplexTypeVariety(childElement)) {
						analyzeComplexTypeVariety(complexType, childElement,
								"", true);
					}

					complexContentHash.put(complexType, elementComplexChild
							.getAttributeValue("base"));

				} else {
					getLogger()
							.error(
									"Error en el elemento del ComplexContent: tiene mas de un hijo");
				}

			} else {
				if (elementComplexChild.getName().toString().equalsIgnoreCase(
						"restriction")) {
					getLogger().error("Tenemos un Restriction");

				}
			}
		}
	}

	private TypeElement processComplexContentRestriction(
			ComplexType complexType, org.jdom.Element elementComplexContent) {

		for (int j = 0; j < elementComplexContent.getChildren().size(); j++) {
			org.jdom.Element elementComplexChild = (org.jdom.Element) elementComplexContent
					.getChildren().get(j);

			if (elementComplexChild.getName().toString().equalsIgnoreCase(
					"restriction")) {

				for (Iterator iterator = elementComplexChild.getChildren()
						.iterator(); iterator.hasNext();) {
					org.jdom.Element child = (org.jdom.Element) iterator.next();
					if (child.getName().toString()
							.equalsIgnoreCase("attribute")) {

						String arrayTypeString = getValueAttribute(child,
								"arrayType");
						String[] arrayType = getTypeString(arrayTypeString);
						String typeString = "";
						String nameSpace = "";
						nameSpace = arrayType[1];
						typeString = arrayType[0];
						typeString = typeString.replace("[]", "");
						TypeElement elementArray = null;
						elementArray = createElementForArray(typeString,
								nameSpace,
								complexType.getName() /* nameString */, /* isNillable */
								false, false/* true */, "", true);
						elementArray.setLista(true);
						return elementArray;
					}
				}
			} else {

				getLogger().error("Elemento no esperado");

			}
		}
		getLogger()
				.error(
						"No se encontró elemento Attribute dentro de ComplexContent de tipo Restriction");
		return null;
	}

	private boolean hasComplexTypeVariety(org.jdom.Element t) {
		if (t.getName().toString().equalsIgnoreCase("sequence")
				|| t.getName().toString().equalsIgnoreCase("all")
				|| t.getName().equalsIgnoreCase("choice")) {
			return true;
		}

		return false;
	}

	private static List<ExtensibilityElement> findExtensibilityElement(
			ElementExtensible extensibleElement, String elementType) {
		List<ExtensibilityElement> listadoSchemas = new ArrayList<ExtensibilityElement>();
		if (extensibleElement != null) {
			List extensibilityElements = extensibleElement
					.getExtensibilityElements();

			if (extensibilityElements != null) {
				for (Object o : extensibilityElements) {
					ExtensibilityElement element = (ExtensibilityElement) o;
					if (element.getElementType().getLocalPart()
							.equalsIgnoreCase(elementType)) {

						listadoSchemas.add(element);
					}
				}
			}
		}
		return listadoSchemas;
	}

	public Map<String, TypeElement> getTypesHash() {
		return typesHash;
	}

	/**
	 * Analiza el String asociado al atributo Type de los elementos. Retorna un
	 * array de dos elementos String. El primero con el tipo. El segundo con el
	 * namespace (si posee).
	 * 
	 * @param atributeTypeString
	 * @return
	 */
	private String[] getTypeString(String atributeTypeString) {
		String[] typesNames = atributeTypeString.split(":");
		String typeString = "";
		String nameSpace = "";
		String[] retorno = new String[2];
		if (typesNames.length == 1) {
			// No posee namespace, solo posee el tipo
			typeString = typesNames[0];

		} else {
			if (typesNames.length == 2) {
				nameSpace = typesNames[0];
				typeString = typesNames[1];
			}
		}
		retorno[0] = typeString;
		retorno[1] = nameSpace;
		return retorno;
	}

	public static Logger getLogger() {
		return logger;
	}
}
